init('ObjectUtils');
(function(){
	var log = require('./Log').create('ObjectUtils');
	var Collections  = require('./Collections');
	var CommandChain = require('./CommandChain');
	
	var recurseProperty = function(property, context){
		var values = property.split('.', 2);
		if(!context) return null;
		if(!context[values[0]]) return null;
		context = context[values[0]];
		if( values.length == 2){
			return recurseProperty(property.substring(property.indexOf('.')+1), context);
		}
		return context;
	};
	var propertyAccessor = function(property){
		return function(object){
			return recurseProperty(property, object);
		}
	};
	var get = function(property){
		return {
			from : propertyAccessor(property)
		};
	};
	var merge = function(clz, parent){
		return merge(clz)(parent, 'test');
	};
	var merge = function(obj){
		var scope = {
			into : function(obj2){
				var result = {};
				for(property in obj2) result[property] = obj2[property];
				for(property in obj ) result[property] = obj[property];
				return result;
			},
			from : function(obj2){
				var result = {};
				for(property in obj ) result[property] = obj[property];
				for(property in obj2) result[property] = obj2[property];
				return result;
			}	
		};
		scope.mapEntry = function(item, name){
			return { key:name, value: scope.into(item) };
		}
		return scope;
	};
	
	var Factory = function(scope, defaultSelector){
		this.scope = scope || this;
		this.defaultSelector = defaultSelector || false;
	};
	Factory.prototype = {
		canCreate : function(obj){
			return this.defaultSelector;
		},
		create : function(obj, callback){
			callback.apply(this.scope, [obj]);
		}
	};
	
	var copy = function(src){
		var _copy = function(){
			this.factoryList = [];
			this.callback = null;
			this.scope    = null;
			this.overwrite = true;
			this.excludeList = [];
		}
		_copy.prototype = {
			shallowInto : function(target){
				this.factoryList.push(new Factory(this.scope, true));
				return copyProperties(target, src, this);
			},
			deepInto : function(target){
				//TODO
			},
			safely : function(){
				this.overwrite = false;
				return this;
			},
			into : function(target){
				return this.shallowInto(target);
			},
			withFactory : function(factory){
				this.factoryList.push(factory);
				return this;
			},
			withCallback : function(callback){
				this.callback = callback;
				return this;
			},
			withScope : function(scope){
				this.scope = scope;
				return this;
			},
			onDone : function(dest){
				if( this.callback )
					this.callback.apply(this.scope, [dest]);
			},
			excluding : function(excludeItem){
				if( excludeItem && !excludeItem.length )
					excludeItem = [excludeItem];
				Collections.List.addAll(this.excludeList, excludeItem);
				return this;
			},
			excludes : function(member, target){
				if( !this.overwrite && target[member] ) return true;
				if( Collections.contains(this.excludeList, member) ) return true;
				return false;
			}
		};
		return new _copy();
	};
	
	var ctorIf = function(copyDesc, objectDesc, callback){
		var done = false;
		Collections.iterate(copyDesc.factoryList, function(factory){
			if(done) return;
			if(factory.canCreate(objectDesc)){
				factory.create(objectDesc, callback);
				done = true;			
			}
		});
		if( !done ){
			log.warn("No copy factory for : ${obj}", {obj:objectDesc});
		}
	};
	
	var copyProperties = function(dest, src, copyDesc){
		log.debug('copyProperties() begin ');
		if( !dest || !src ){
			copyDesc.onDone(dest);
		}
		var chain = CommandChain.create();
		for(member in src){
			if( copyDesc.excludes(member, dest) ){
				continue;
			}
			log.trace('\tcopying ${member}', {member:member});
			var updateProperty = function(){	
				var tmp = member;
				return function(value){
					log.debug('\tdone copy ${member}', {member:tmp, value:value});
					dest[tmp] = value;
					chain.proceed();
				}
			};
			chain.invoke(ctorIf, [copyDesc, src[member], updateProperty()], copyDesc.scope);
		}
		chain.execute(function(){
			log.debug('copyProperties() done ');
			copyDesc.onDone(dest);
		});
	};
	
	var inherit = function(clz, args){
		copy( clz.apply(this, args) ).safely().into(this);
		copy( clz.prototype ).safely().into(this);
	};
	
	exports.inherit = inherit;
	exports.recurseProperty = recurseProperty;
	exports.get = get;
	exports.propertyAccessor = propertyAccessor;
	exports.merge = merge;
	exports.copy  = copy;
})();